<!DOCTYPE html>
<title>Image sampling techniques</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/canvaskit.js"></script>

<style>
    #draw {
        border: 1px dashed grey;
    }
    figcaption {
        max-width: 800px;
    }
</style>

<body>
  <h1>Image sampling techniques</h1>

  <div class="slidecontainer">
      <input style="width:400px;" type="range" min="-10" max="10" value="0" step="0.01"
             class="slider" id="scale_slider" title="Scale">
  </div>

  <figure>
      <!-- width/height hard-coded for grid of 256px images. -->
      <div ondrop="dropHandler(event);" ondragover="dragOverHandler(event);">
          <canvas id="draw" width=868 height=592></canvas>
      </div>
    <figcaption>
        Drop an Image onto the rectangle above
    </figcaption>
  </figure>

</body>

<script type="text/javascript" charset="utf-8">
    function preventScrolling(elem) {
        elem.addEventListener('touchmove', (e) => {
        // Prevents touch events in the element from scrolling.
        e.preventDefault();
        e.stopPropagation();
      });
    }

    function dragOverHandler(ev) { ev.preventDefault(); }

    // these can be changed with click/drag on the primary rectangle
    let cell_width = 256,
        cell_height = 256;

    let image = null;
    let CK = null;
    let surface = null;
    let drawFrame = null;

    function dropHandler(ev) {
      ev.preventDefault();

      let file = null;
      if (ev.dataTransfer.items) {
        // Use DataTransferItemList interface to access the file(s)
        for (item of ev.dataTransfer.items) {
          // If dropped items aren't files, reject them
          if (item.kind === 'file') {
            file = item.getAsFile();
            break;
          }
        }
      } else {
        // Use DataTransfer interface to access the file(s)
        for (f of ev.dataTransfer.files) {
          file = f;
          break;
        }
      }
      if (file) {
          file.arrayBuffer().then(buffer => {
              image = CK.MakeImageFromEncoded(buffer);
              surface.requestAnimationFrame(drawFrame);
          });
      }
    }

  const ckLoaded = CanvasKitInit({ locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/' + file });

  ckLoaded.then((_CK) => {
    CK = _CK;
    surface = CK.MakeCanvasSurface('draw');
    if (!surface) {
      throw 'Could not make surface';
    }

    const font = new CK.Font(null, 15);
    const paint = new CK.Paint();
    const textPaint = new CK.Paint();
    const TX = 20,
          TY = 30;  // where we start the first rectangle

    scale_slider.oninput = () => { surface.requestAnimationFrame(drawFrame); };

    m33_scaled = function(sx, sy) {
        if (!sy) sy = sx;
        let m = new Float32Array(9);
        m[0] = sx; m[4] = sy; m[8] = 1;
        return m;
    }

    drawFrame = function(canvas) {
        if (!image) return;

        const scale = scale_slider.valueAsNumber < 0 ? 1 / (1 - scale_slider.valueAsNumber)
                                                     : (1 + scale_slider.valueAsNumber);

        const bounds = [0, 0, cell_width, cell_height];

        const lm = m33_scaled(scale, scale);

       const samplings = [
            [CK.FilterMode.Nearest, CK.MipmapMode.None,   0, 0, "Nearest"],
            [CK.FilterMode.Linear,  CK.MipmapMode.None,   0, 0, "Bilerp"],
            [CK.FilterMode.Linear,  CK.MipmapMode.Linear, 0, 0, "Trilerp"],
            [null, null,     0,   0.5, "CatmullRom"],
            [null, null, 1/3.0, 1/3.0, "Mitchell"],
        ];

        const tile = CK.TileMode.Repeat;

        canvas.save();
        canvas.translate(TX, TY);
        canvas.save();
        for (i in samplings) {
            if (i == 3) {
                canvas.restore();
                canvas.translate(0, bounds[3] - bounds[1] + 30);
                canvas.save();
            }

            const s = samplings[i];
            const shader = s[0] ? image.makeShaderOptions(tile, tile, s[0], s[1], lm)
                                : image.makeShaderCubic(  tile, tile, s[2], s[3], lm);
            paint.setShader(shader);
            canvas.drawRect(bounds, paint);
            shader.delete();

            canvas.drawText(s[4], 20, -8, textPaint, font);

            canvas.translate(bounds[2] - bounds[0] + 30, 0);
        }
        canvas.restore();
        canvas.restore();
        // draw the drag handle
        if (true) {
            canvas.save();
            paint.setShader(null);
            paint.setColor(CK.Color4f(1, 0, 0));
            paint.setStrokeWidth(2);
            canvas.translate(TX + cell_width + 4, TY + cell_height + 4);
            canvas.drawLine(-12,   0, 0, 0, paint);
            canvas.drawLine(  0, -12, 0, 0, paint);
            canvas.restore()
        }
    };

    // register our mouse handler
    {
        function len2(x, y) {
            return x*x + y*y;
        }
        function hit_test(x,y, x1,y1) {
            return len2(x-x1, y-y1) <= 10*10;
        }

        let do_drag = false;
        function pointer_up(e) {
            do_drag = false;
         }
        function pointer_down(e) {
            do_drag = hit_test(TX+cell_width, TY+cell_height, e.offsetX, e.offsetY);
         }
        function pointer_move(e) {
          if (e.pressure && do_drag) {
              cell_width  = Math.max(e.offsetX - TX, 32);
              cell_height = Math.max(e.offsetY - TY, 32);
              surface.requestAnimationFrame(drawFrame);
           }
        }

        const elem = document.getElementById('draw');
        elem.addEventListener('pointermove', pointer_move);
        elem.addEventListener('pointerdown', pointer_down);
        elem.addEventListener('pointerup', pointer_up);
        preventScrolling(elem);
    }

    surface.requestAnimationFrame(drawFrame);
  });

</script>
